﻿using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using EU.Core.Common.Extensions;
using Mapster;

namespace EU.Core.Common.Helper;

#region 动态linq帮助类，连接符号，运算符号

/// <summary>
/// 动态linq工厂
/// </summary>
public static class DynamicLinqFactory
{
    private static readonly Dictionary<string, OperationSymbol> _operatingSystems = new Dictionary<string, OperationSymbol>();
    public static Dictionary<string, OperationSymbol> OperatingSystems => GetOperationSymbol();

    private static readonly Dictionary<string, LinkSymbol> _linkSymbols = new Dictionary<string, LinkSymbol>();
    public static Dictionary<string, LinkSymbol> LinkSymbols => GetLinkSymbol();

    /// <summary>
    /// 生成lambd表达式(如:CompanyID != 1 & CompanyID == 1)
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <param name="propertyStr"></param>
    /// <returns></returns>
    public static Expression<Func<TSource, bool>> CreateLambda<TSource>(string propertyStr)
    {
        // 设置自定义lanbd
        // 定义 lanbd 种子（p=> xxxxxx）中的 p
        if (string.IsNullOrWhiteSpace(propertyStr))
            return LinqHelper.True<TSource>(); //为空就返回空的表达式

        var parameter = Expression.Parameter(typeof(TSource), "p");
        var strArr = SplitOperationSymbol(propertyStr);


        // 第一个判断条件，固定一个判断条件作为最左边
        Expression mainExpressin = ExpressionStudio(null, strArr[0], parameter);
        // 将需要放置在最左边的判断条件从列表中去除，因为已经合成到表达式最左边了
        strArr.RemoveAt(0);

        foreach (var x in strArr)
        {
            mainExpressin = ExpressionStudio(mainExpressin, x, parameter);
        }

        return mainExpressin.ToLambda<Func<TSource, bool>>(parameter);
    }

    /// <summary>
    /// 组合条件判断表达式
    /// </summary>
    /// <param name="left">左边的表达式</param>
    /// <param name="dynamicLinq"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static Expression ExpressionStudio(Expression left, DynamicLinqHelper dynamicLinq, ParameterExpression key)
    {
        Expression mainExpression = key;

        if (!dynamicLinq.Left.IsNullOrEmpty())
        {
            var properties = dynamicLinq.Left.Split('.');

            int index = 0;
            foreach (var t in properties)
            {
                if (mainExpression.Type.HasImplementedRawGeneric(typeof(IEnumerable<>)))
                {
                    return ExpressionStudioEnumerable(left, mainExpression, dynamicLinq.Adapt<DynamicLinqHelper>(),
                        properties.Skip(index).ToArray());
                }

                mainExpression = mainExpression.Property(t);
                index++;
            }
        }

        Expression right = null;
        if (dynamicLinq.IsMerge && dynamicLinq.Child.Any())
        {
            right = ExpressionStudio(null, dynamicLinq.Child[0], key);
            for (var i = 1; i < dynamicLinq.Child.Count; i++)
            {
                right = ChangeLinkSymbol(dynamicLinq.Child[i].LinkSymbol, right, ExpressionStudio(null, dynamicLinq.Child[i], key));
            }
        }
        else
        {
            right = ChangeOperationSymbol(dynamicLinq.OperationSymbol, mainExpression, dynamicLinq.Right);
        }

        left = left == null
            // 如果左边表达式为空，则当前的表达式就为最左边
            ? right
            // 如果不为空，则将当前的表达式连接到左边
            : ChangeLinkSymbol(dynamicLinq.LinkSymbol, left, right);
        return left;
    }

    public static Expression ExpressionStudioEnumerable(Expression left, Expression property, DynamicLinqHelper dynamicLinq, string[] properties)
    {
        var realType = property.Type.GenericTypeArguments[0];

        var parameter = Expression.Parameter(realType, "z");
        Expression mainExpression = property;
        if (!properties.Any())
        {
            throw new ApplicationException("条件表达式错误,属性为集合时,需要明确具体属性");
        }

        dynamicLinq.Left = string.Join(".", properties);
        mainExpression = ExpressionStudio(null, dynamicLinq, parameter);

        var lambda = Expression.Lambda(mainExpression, parameter);

        mainExpression = Expression.Call(typeof(Enumerable), "Any", new[] {realType}, property, lambda);

        left = left == null
            ? mainExpression
            : ChangeLinkSymbol(dynamicLinq.LinkSymbol, left, mainExpression);

        return left;
    }


    public static List<DynamicLinqHelper> SplitOperationSymbol(string str)
    {
        var outList = new List<DynamicLinqHelper>();
        var tokens = Regex.Matches(FormatString(str), _pattern, RegexOptions.Compiled)
            .Select(m => m.Groups[1].Value.Trim())
            .ToList();
        SplitOperationSymbol(tokens, outList);
        return outList;
    }

    private static void SplitOperationSymbol(List<string> tokens, List<DynamicLinqHelper> outList, int start = 0, int end = 0)
    {
        var dys = new Stack<DynamicLinqHelper>();
        var dynamicLinqHelper = new DynamicLinqHelper();
        if (end == 0)
        {
            end = tokens.Count - 1;
        }

        for (int i = start; i <= end; i++)
        {
            var token = tokens[i];

            if (LinkSymbols.TryGetValue(token, out var symbol))
            {
                if (dys.Count > 0)
                {
                    var linqHelper = dys.Peek();
                    linqHelper.Child.Add(dynamicLinqHelper);
                }
                else
                {
                    outList.Add(dynamicLinqHelper);
                }

                dynamicLinqHelper = new DynamicLinqHelper()
                {
                    LinkSymbol = symbol,
                };
                continue;
            }

            if (OperatingSystems.TryGetValue(token.ToLower(), out var system))
            {
                dynamicLinqHelper!.OperationSymbol = system;
                continue;
            }


            if (dynamicLinqHelper!.OperationSymbol != OperationSymbol.In)
            {
                if (string.Equals(token.Trim(), "("))
                {
                    dynamicLinqHelper!.IsMerge = true;
                    dynamicLinqHelper.Child = new List<DynamicLinqHelper>();
                    dys.Push(dynamicLinqHelper);
                    dynamicLinqHelper = new DynamicLinqHelper();
                    continue;
                }

                if (string.Equals(token.Trim(), ")"))
                {
                    if (dys.Count > 1)
                    {
                        var dya = dys.Pop();
                        dya.Child.Add(dynamicLinqHelper);

                        dynamicLinqHelper = dya;
                        continue;
                    }
                    else
                    {
                        var dya = dys.Pop();
                        dya.Child.Add(dynamicLinqHelper);
                        outList.Add(dya);
                        dynamicLinqHelper = null;
                        continue;
                    }
                }
            }


            if (dynamicLinqHelper!.OperationSymbol is null)
            {
                dynamicLinqHelper.Left += token;
            }
            else
            {
                dynamicLinqHelper.Right += FormatValue(token);
            }

            if (i == end)
            {
                outList.Add(dynamicLinqHelper);
                dynamicLinqHelper = null;
            }
        }
    }

    public static string FormatValue(string str)
    {
        return str.TrimStart('"').TrimEnd('"');
        // return str.TrimStart('"').TrimEnd('"').Replace(@"\""", @"""");
    }


    /// <summary>
    /// 将运算枚举符号转成具体使用方法
    /// </summary>
    public static Expression ChangeLinkSymbol(LinkSymbol symbol, Expression left, Expression right)
    {
        switch (symbol)
        {
            case LinkSymbol.OrElse:
                return left.OrElse(right);
            case LinkSymbol.AndAlso:
                return left.AndAlso(right);
            default:
                return left;
        }
    }

    public static Dictionary<string, OperationSymbol> GetOperationSymbol()
    {
        if (_operatingSystems.Any()) return _operatingSystems;

        var fielding = typeof(OperationSymbol).GetFields();
        foreach (var item in fielding)
        {
            if (item.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute attr && !attr.Name.IsNullOrEmpty())
            {
                foreach (var name in attr.Name.Split(';'))
                {
                    _operatingSystems.Add(name.ToLower(), (OperationSymbol) item.GetValue(null));
                }
            }
        }

        return _operatingSystems;
    }

    public static Dictionary<string, LinkSymbol> GetLinkSymbol()
    {
        if (_linkSymbols.Any()) return _linkSymbols;

        var fielding = typeof(LinkSymbol).GetFields();
        foreach (var item in fielding)
        {
            if (item.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute attr && !attr.Name.IsNullOrEmpty())
            {
                foreach (var name in attr.Name.Split(';'))
                {
                    _linkSymbols.Add(name, (LinkSymbol) item.GetValue(null));
                }
            }
        }

        return _linkSymbols;
    }


    public static string FormatString(string str)
    {
        var sb = new StringBuilder();
        var firstIndex = -1;
        var lastIndex = -1;
        for (var i = 0; i < str.Length; i++)
        {
            var character = str[i];

            if (firstIndex == -1)
            {
                if (character.IsNullOrEmpty() && i < str.Length - 2)
                    if ('"'.Equals(str[i + 1]))
                        firstIndex = i + 1;
            }
            else
            {
                if ('\"'.Equals(character))
                {
                    var andIndex = str.IndexOf("\" &", firstIndex);
                    var orIndex = str.IndexOf("\" |", firstIndex);
                    var andOrIndex = Math.Min(andIndex, orIndex);
                    andOrIndex = andOrIndex == -1 ? Math.Max(andOrIndex, orIndex) : andOrIndex;

                    if (andOrIndex != -1)
                    {
                        lastIndex = andOrIndex;
                    }
                    else
                    {
                        if (i == firstIndex) continue;
                        if (i == str.Length - 1 || str[i + 1].IsNullOrEmpty()) lastIndex = i;
                    }
                }

                if (lastIndex != -1)
                {
                    var temp = str.Substring(firstIndex + 1, lastIndex - firstIndex - 1).Replace(@"""", @"\""");
                    sb.Append($" \"{temp}\" ");

                    i = lastIndex;
                    firstIndex = -1;
                    lastIndex = -1;
                    continue;
                }
            }

            if (firstIndex != -1) continue;

            sb.Append(character);
        }

        return sb.ToString();
    }

    /// <summary>tokenizer pattern: Optional-SpaceS...Token...Optional-Spaces</summary>
    public static readonly string _pattern = @"\s*(" + string.Join("|", new string[]
    {
        // operators and punctuation that are longer than one char: longest first
        string.Join("|", new[]
        {
            "||", "&&", "==", "!=", "<=", ">=",
            "in",
            "like", "contains", "%=",
            "startslike", "StartsLike", "startscontains", "StartsContains", "%>",
            "endlike", "EndLike", "endcontains", "EndContains", "%<",
        }.Select(Regex.Escape)),
        @"""(?:\\.|[^""])*""", // string
        @"\d+(?:\.\d+)?",      // number with optional decimal part
        @"\w+",                // word
        @"\S",                 // other 1-char tokens (or eat up one character in case of an error)
    }) + @")\s*";


    /// <summary>
    /// 将运算枚举符号转成具体使用方法
    /// </summary>
    public static Expression ChangeOperationSymbol(OperationSymbol? symbol, Expression key, object right)
    {
        // 将右边数据类型强行转换成左边一样的类型
        // 两者如果Type不匹配则无法接下去的运算操作，抛出异常
        object newTypeRight;
        if (right == null || string.IsNullOrEmpty(right.ToString()) || right.ToString() == "null")
        {
            newTypeRight = null;
        }
        else
        {
            if (symbol == OperationSymbol.In)
            {
                newTypeRight = right.ChangeTypeList(key.Type);
            }
            else
            {
                newTypeRight = right.ChangeType(key.Type);
            }
        }


        // 根据当前枚举类别判断使用那种比较方法
        switch (symbol)
        {
            case OperationSymbol.Equal:
                return key.Equal(Expression.Constant(newTypeRight));
            case OperationSymbol.GreaterThan:
            {
                if (key.Type == typeof(string))
                    return key.Contains(Expression.Constant(newTypeRight)); //对string 特殊处理  由于string
                return key.GreaterThan(Expression.Constant((newTypeRight)));
            }
            case OperationSymbol.GreaterThanOrEqual:
            {
                if (key.Type == typeof(string))
                    return key.Contains(Expression.Constant(newTypeRight, typeof(string)));
                return key.GreaterThanOrEqual(Expression.Constant(newTypeRight));
            }

            case OperationSymbol.LessThan:
            {
                if (key.Type == typeof(string))
                    return key.Contains(Expression.Constant(newTypeRight, typeof(string)));
                return key.LessThan(Expression.Constant((newTypeRight)));
            }
            case OperationSymbol.LessThanOrEqual:
            {
                if (key.Type == typeof(string))
                    return key.Contains(Expression.Constant(newTypeRight, typeof(string)));
                return key.LessThanOrEqual(Expression.Constant((newTypeRight)));
            }
            case OperationSymbol.NotEqual:
                return key.NotEqual(Expression.Constant(newTypeRight));
            case OperationSymbol.Contains:
                return key.Contains(Expression.Constant(newTypeRight));
            case OperationSymbol.StartsContains:
                return key.StartContains(Expression.Constant(newTypeRight));
            case OperationSymbol.EndContains:
                return key.EndContains(Expression.Constant(newTypeRight));
            case OperationSymbol.In:
                return Expression.Constant(newTypeRight).Contains(key);
            default:
                throw new ArgumentException("OperationSymbol IS NULL");
        }
    }
}

/// <summary>
/// 动态linq帮助类
/// </summary>
public class DynamicLinqHelper
{
    [Display(Name = "左")]
    public string Left { get; set; }

    [Display(Name = "右")]
    public string Right { get; set; }

    [Display(Name = "运算符")]
    public OperationSymbol? OperationSymbol { get; set; }

    [Display(Name = "连接符")]
    public LinkSymbol LinkSymbol { get; set; }

    /// <summary>
    /// 是否是合并 用于括号
    /// </summary>
    public bool IsMerge { get; set; } = false;

    /// <summary>
    /// 再有括号时候使用
    /// </summary>
    public List<DynamicLinqHelper> Child { get; set; }
}

/// <summary>
/// 连接符枚举（将来可能会包含 括号 ）
/// </summary>
public enum LinkSymbol
{
    [Display(Name = "&&;&")]
    AndAlso,

    [Display(Name = "||;|")]
    OrElse,

    Empty
}

/// <summary>
/// 常用比较运算符 > , >= , == , < , <= , != ,Contains
/// </summary>
public enum OperationSymbol
{
    [Display(Name = "in")]
    In,

    [Display(Name = "like;contains;%=")]
    Contains,

    [Display(Name = "StartsLike;StartsContains;%>")]
    StartsContains,

    [Display(Name = "EndLike;EndContains;%<")]
    EndContains,

    [Display(Name = ">")]
    GreaterThan,

    [Display(Name = ">=")]
    GreaterThanOrEqual,

    [Display(Name = "<")]
    LessThan,

    [Display(Name = "<=")]
    LessThanOrEqual,

    [Display(Name = "==;=")]
    Equal,

    [Display(Name = "!=")]
    NotEqual
}

#endregion


/// <summary>
/// Queryable扩展
/// </summary>
public static class QueryableExtensions
{
    #region 自定义扩展Queryable

    /// <summary>
    /// Where扩展
    /// </summary>
    public static IEnumerable<TSource> IWhere<TSource>(this IEnumerable<TSource> source, string linqStr)
    {
        return source.Where(DynamicLinqFactory.CreateLambda<TSource>(linqStr).Compile());
    }

    /// <summary>
    /// FirstOrDefault扩展
    /// </summary>
    public static TSource IFirstOrDefault<TSource>(this IEnumerable<TSource> source, string linqStr)
    {
        return source.FirstOrDefault(DynamicLinqFactory.CreateLambda<TSource>(linqStr).Compile());
    }

    /// <summary>
    /// Count扩展
    /// </summary>
    public static Int32 ICount<TSource>(this IEnumerable<TSource> source, string linqStr)
    {
        return source.Count(DynamicLinqFactory.CreateLambda<TSource>(linqStr).Compile());
    }

    /// <summary>
    /// 自定义排序
    /// </summary>
    public static IOrderedQueryable<TSource> ISort<TSource>(this IQueryable<TSource> source, string orderByProperty, bool asc)
    {
        string command = asc ? "OrderBy" : "OrderByDescending";
        var type = typeof(TSource);
        var property = type.GetProperty(orderByProperty);
        var parameter = Expression.Parameter(type, "p");
        var propertyAccess = Expression.MakeMemberAccess(parameter, property);
        var orderByExpression = Expression.Lambda(propertyAccess, parameter);
        var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] {type, property.PropertyType}, source.Expression,
            Expression.Quote(orderByExpression));
        return (IOrderedQueryable<TSource>) source.Provider.CreateQuery<TSource>(resultExpression);
    }

    /// <summary>
    /// 自定义分页
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <param name="source"></param>
    /// <param name="nowPage"></param>
    /// <param name="pageSize"></param>
    /// <returns></returns>
    public static IQueryable<TSource> IPaging<TSource>(this IQueryable<TSource> source, int nowPage, int pageSize)
    {
        return source.ISkip((nowPage - 1) * pageSize).ITake(pageSize);
    }

    /// <summary>
    /// 自定义Skip
    /// </summary>
    public static IQueryable<TSource> ISkip<TSource>(this IQueryable<TSource> source, int count)
    {
        return source.Provider.CreateQuery<TSource>(Expression.Call(
            // 类别
            typeof(Queryable),
            // 调用的方法
            "Skip",
            // 元素类别
            new Type[] {source.ElementType},
            // 调用的表达树
            source.Expression,
            // 参数
            Expression.Constant(count)));
    }

    /// <summary>
    /// 自定义Take
    /// </summary>
    public static IQueryable<TSource> ITake<TSource>(this IQueryable<TSource> source, int count)
    {
        return source.Provider.CreateQuery<TSource>(Expression.Call(
            // 类别
            typeof(Queryable),
            // 调用的方法
            "Take",
            // 元素类别
            new Type[] {source.ElementType},
            // 调用的表达树
            source.Expression,
            // 参数
            Expression.Constant(count)));
    }

    /// <summary>
    /// 自定义去重复
    /// </summary>
    public static IEnumerable<TSource> IDistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        var seenKeys = new HashSet<TKey>();
        return source.Where(element => seenKeys.Add(keySelector(element)));
    }

    /// <summary>
    /// 动态赋值
    /// </summary>
    public static void CopyTo<T>(this object source, T target) where T : class, new()
    {
        if (source == null)
            return;

        if (target == null)
        {
            target = new T();
        }

        foreach (var property in target.GetType().GetProperties())
        {
            // 这里可以判断一下当前属性值是否为空的 source.GetType().GetProperty(property.Name).GetValue(source, null)
            target.GetType().InvokeMember(property.Name, BindingFlags.SetProperty, null, target,
                new object[] {source.GetType().GetProperty(property.Name).GetValue(source, null)});
        }
    }

    /// <summary>
    /// 移除特殊字段数据
    /// </summary>
    public static void RemoveSpecialPropertyValue(this object source)
    {
        var properties = source.GetType().GetProperties();
        foreach (var x in properties)
        {
            if (x.GetAccessors().Any(y => y.IsVirtual))
            {
                source.GetType().GetProperty(x.Name).SetValue(source, null, null);
            }
        }
    }

    #endregion
}